#pragma once

#include <algorithm>
#include <cctype>
#include <string>
#include <functional>
#include <ctime>
#include <tuple>
#include <memory>
#include <vector>

#define MODERN_SQLITE_VERSION 3002008

#ifdef __has_include
#if __cplusplus > 201402 && __has_include(<optional>)
#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#elif __has_include(<experimental/optional>)
#define MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
#endif
#endif

#ifdef __has_include
#if __cplusplus > 201402 && __has_include(<variant>)
#define MODERN_SQLITE_STD_VARIANT_SUPPORT
#endif
#endif

#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#include <optional>
#endif

#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
#include <experimental/optional>
#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#endif

#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
#include <boost/optional.hpp>
#endif

#include "../sqlite/sqlite3.h"

#include "sqlite_modern_cpp/errors.h"
#include "sqlite_modern_cpp/utility/function_traits.h"
#include "sqlite_modern_cpp/utility/uncaught_exceptions.h"
#include "sqlite_modern_cpp/utility/utf16_utf8.h"

#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
#include "sqlite_modern_cpp/utility/variant.h"
#endif

namespace CQLIB {
	namespace CQSQL {

		// std::optional support for NULL values
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
		template<class T>
		using optional = std::experimental::optional<T>;
#else
		template<class T>
		using optional = std::optional<T>;
#endif
#endif

		class database;
		class database_binder;

		template<std::size_t> class binder;

		typedef std::shared_ptr<sqlite3> connection_type;

		template<typename Tuple, int Element = 0, bool Last = (std::tuple_size<Tuple>::value == Element)> struct tuple_iterate {
			static void iterate(Tuple& t, database_binder& db) {
				get_col_from_db(db, Element, std::get<Element>(t));
				tuple_iterate<Tuple, Element + 1>::iterate(t, db);
			}
		};

		template<typename Tuple, int Element> struct tuple_iterate<Tuple, Element, true> {
			static void iterate(Tuple&, database_binder&) {}
		};

		class database_binder {

		public:
			// database_binder is not copyable
			database_binder() = delete;
			database_binder(const database_binder& other) = delete;
			database_binder& operator=(const database_binder&) = delete;

			database_binder(database_binder&& other) :
				_db(std::move(other._db)),
				_stmt(std::move(other._stmt)),
				_inx(other._inx), execution_started(other.execution_started) { }

			void execute() {
				_start_execute();
				int hresult;

				while ((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {}

				if (hresult != SQLITE_DONE) {
					errors::throw_sqlite_error(hresult, sql());
				}
			}

			std::string sql() {
#if SQLITE_VERSION_NUMBER >= 3014000
				auto sqlite_deleter = [](void* ptr) {sqlite3_free(ptr); };
				std::unique_ptr<char, decltype(sqlite_deleter)> str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter);
				return str ? str.get() : original_sql();
#else
				return original_sql();
#endif
			}

			std::string original_sql() {
				return sqlite3_sql(_stmt.get());
			}

			void used(bool state) {
				if (!state) {
					// We may have to reset first if we haven't done so already:
					_next_index();
					--_inx;
				}
				execution_started = state;
			}
			bool used() const { return execution_started; }

		private:
			std::shared_ptr<sqlite3> _db;
			std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> _stmt;
			utility::UncaughtExceptionDetector _has_uncaught_exception;

			int _inx;

			bool execution_started = false;

			int _next_index() {
				if (execution_started && !_inx) {
					sqlite3_reset(_stmt.get());
					sqlite3_clear_bindings(_stmt.get());
				}
				return ++_inx;
			}
			void _start_execute() {
				_next_index();
				_inx = 0;
				used(true);
			}

			void _extract(std::function<void(void)> call_back) {
				int hresult;
				_start_execute();

				while ((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {
					call_back();
				}

				if (hresult != SQLITE_DONE) {
					errors::throw_sqlite_error(hresult, sql());
				}
			}

			void _extract_single_value(std::function<void(void)> call_back) {
				int hresult;
				_start_execute();

				if ((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {
					call_back();
				}
				else if (hresult == SQLITE_DONE) {
					throw errors::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE);
				}

				if ((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {
					throw errors::more_rows("not all rows extracted", sql(), SQLITE_ROW);
				}

				if (hresult != SQLITE_DONE) {
					errors::throw_sqlite_error(hresult, sql());
				}
			}

			sqlite3_stmt* _prepare(const std::u16string& sql) {
				return _prepare(utility::utf16_to_utf8(sql));
			}

			sqlite3_stmt* _prepare(const std::string& sql) {
				int hresult;
				sqlite3_stmt* tmp = nullptr;
				const char* remaining;
				hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, &remaining);
				if (hresult != SQLITE_OK) errors::throw_sqlite_error(hresult, sql);
				if (!std::all_of(remaining, sql.data() + sql.size(), [](char ch) {return std::isspace(ch); }))
					throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql);
				return tmp;
			}

			template <typename Type>
			struct is_sqlite_value : public std::integral_constant<
				bool,
				std::is_floating_point<Type>::value
				|| std::is_integral<Type>::value
				|| std::is_same<std::string, Type>::value
				|| std::is_same<std::u16string, Type>::value
				|| std::is_same<sqlite_int64, Type>::value
			> { };
			template <typename Type, typename Allocator>
			struct is_sqlite_value< std::vector<Type, Allocator> > : public std::integral_constant<
				bool,
				std::is_floating_point<Type>::value
				|| std::is_integral<Type>::value
				|| std::is_same<sqlite_int64, Type>::value
			> { };
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
			template <typename ...Args>
			struct is_sqlite_value< std::variant<Args...> > : public std::integral_constant<
				bool,
				true
			> { };
#endif


			/* for vector<T, A> support */
			template<typename T, typename A> friend database_binder& operator <<(database_binder& db, const std::vector<T, A>& val);
			template<typename T, typename A> friend void get_col_from_db(database_binder& db, int inx, std::vector<T, A>& val);
			/* for nullptr & unique_ptr support */
			friend database_binder& operator <<(database_binder& db, std::nullptr_t);
			template<typename T> friend database_binder& operator <<(database_binder& db, const std::unique_ptr<T>& val);
			template<typename T> friend void get_col_from_db(database_binder& db, int inx, std::unique_ptr<T>& val);
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
			template<typename ...Args> friend database_binder& operator <<(database_binder& db, const std::variant<Args...>& val);
			template<typename ...Args> friend void get_col_from_db(database_binder& db, int inx, std::variant<Args...>& val);
#endif
			template<typename T> friend T operator++(database_binder& db, int);
			// Overload instead of specializing function templates (http://www.gotw.ca/publications/mill17.htm)
			friend database_binder& operator<<(database_binder& db, const int& val);
			friend void get_col_from_db(database_binder& db, int inx, int& val);
			friend database_binder& operator <<(database_binder& db, const sqlite_int64& val);
			friend void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i);
			friend database_binder& operator <<(database_binder& db, const float& val);
			friend void get_col_from_db(database_binder& db, int inx, float& f);
			friend database_binder& operator <<(database_binder& db, const double& val);
			friend void get_col_from_db(database_binder& db, int inx, double& d);
			friend void get_col_from_db(database_binder& db, int inx, std::string& s);
			friend database_binder& operator <<(database_binder& db, const std::string& txt);
			friend void get_col_from_db(database_binder& db, int inx, std::u16string& w);
			friend database_binder& operator <<(database_binder& db, const std::u16string& txt);


#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
			template <typename OptionalT> friend database_binder& operator <<(database_binder& db, const optional<OptionalT>& val);
			template <typename OptionalT> friend void get_col_from_db(database_binder& db, int inx, optional<OptionalT>& o);
#endif

#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
			template <typename BoostOptionalT> friend database_binder& operator <<(database_binder& db, const boost::optional<BoostOptionalT>& val);
			template <typename BoostOptionalT> friend void get_col_from_db(database_binder& db, int inx, boost::optional<BoostOptionalT>& o);
#endif

		public:

			database_binder(std::shared_ptr<sqlite3> db, std::u16string const& sql) :
				_db(db),
				_stmt(_prepare(sql), sqlite3_finalize),
				_inx(0) {
			}

			database_binder(std::shared_ptr<sqlite3> db, std::string const& sql) :
				_db(db),
				_stmt(_prepare(sql), sqlite3_finalize),
				_inx(0) {
			}

			~database_binder() noexcept(false) {
				/* Will be executed if no >>op is found, but not if an exception
				is in mid flight */
				if (!used() && !_has_uncaught_exception && _stmt) {
					execute();
				}
			}

			template <typename Result>
			typename std::enable_if<is_sqlite_value<Result>::value, void>::type operator>>(
				Result& value) {
				this->_extract_single_value([&value, this] {
					get_col_from_db(*this, 0, value);
					});
			}

			template<typename... Types>
			void operator>>(std::tuple<Types...>&& values) {
				this->_extract_single_value([&values, this] {
					tuple_iterate<std::tuple<Types...>>::iterate(values, *this);
					});
			}

			template <typename Function>
			typename std::enable_if<!is_sqlite_value<Function>::value, void>::type operator>>(
				Function&& func) {
				typedef utility::function_traits<Function> traits;

				this->_extract([&func, this]() {
					binder<traits::arity>::run(*this, func);
					});
			}
		};

		namespace sql_function_binder {
			template<
				typename    ContextType,
				std::size_t Count,
				typename    Functions
			>
				inline void step(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals
				);

			template<
				std::size_t Count,
				typename    Functions,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals,
					Values&&...      values
				);

			template<
				std::size_t Count,
				typename    Functions,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step(
					sqlite3_context* db,
					int,
					sqlite3_value**,
					Values&&...      values
				);

			template<
				typename    ContextType,
				typename    Functions
			>
				inline void final(sqlite3_context* db);

			template<
				std::size_t Count,
				typename    Function,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals,
					Values&&...      values
				);

			template<
				std::size_t Count,
				typename    Function,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar(
					sqlite3_context* db,
					int,
					sqlite3_value**,
					Values&&...      values
				);
		}

		enum class OpenFlags {
			READONLY = SQLITE_OPEN_READONLY,
			READWRITE = SQLITE_OPEN_READWRITE,
			CREATE = SQLITE_OPEN_CREATE,
			NOMUTEX = SQLITE_OPEN_NOMUTEX,
			FULLMUTEX = SQLITE_OPEN_FULLMUTEX,
			SHAREDCACHE = SQLITE_OPEN_SHAREDCACHE,
			PRIVATECACH = SQLITE_OPEN_PRIVATECACHE,
			URI = SQLITE_OPEN_URI
		};
		inline OpenFlags operator|(const OpenFlags& a, const OpenFlags& b) {
			return static_cast<OpenFlags>(static_cast<int>(a) | static_cast<int>(b));
		}
		enum class Encoding {
			ANY = SQLITE_ANY,
			UTF8 = SQLITE_UTF8,
			UTF16 = SQLITE_UTF16
		};
		struct sqlite_config {
			OpenFlags flags = OpenFlags::READWRITE | OpenFlags::CREATE;
			const char* zVfs = nullptr;
			Encoding encoding = Encoding::ANY;
		};

		class database {
		protected:
			std::shared_ptr<sqlite3> _db;

		public:
			database(const std::string& db_name, const sqlite_config& config = {}) : _db(nullptr) {
				sqlite3* tmp = nullptr;
				auto ret = sqlite3_open_v2(db_name.data(), &tmp, static_cast<int>(config.flags), config.zVfs);
				_db = std::shared_ptr<sqlite3>(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed.
				if (ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret);
				sqlite3_extended_result_codes(_db.get(), true);
				if (config.encoding == Encoding::UTF16)
					*this << R"(PRAGMA encoding = "UTF-16";)";
			}

			database(const std::u16string& db_name, const sqlite_config& config = {}) : _db(nullptr) {
				auto db_name_utf8 = utility::utf16_to_utf8(db_name);
				sqlite3* tmp = nullptr;
				auto ret = sqlite3_open_v2(db_name_utf8.data(), &tmp, static_cast<int>(config.flags), config.zVfs);
				_db = std::shared_ptr<sqlite3>(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed.
				if (ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret);
				sqlite3_extended_result_codes(_db.get(), true);
				if (config.encoding != Encoding::UTF8)
					*this << R"(PRAGMA encoding = "UTF-16";)";
			}

			database(std::shared_ptr<sqlite3> db) :
				_db(db) {}

			database_binder operator<<(const std::string& sql) {
				return database_binder(_db, sql);
			}

			database_binder operator<<(const char* sql) {
				return *this << std::string(sql);
			}

			database_binder operator<<(const std::u16string& sql) {
				return database_binder(_db, sql);
			}

			database_binder operator<<(const char16_t* sql) {
				return *this << std::u16string(sql);
			}

			connection_type connection() const { return _db; }

			sqlite3_int64 last_insert_rowid() const {
				return sqlite3_last_insert_rowid(_db.get());
			}

			template <typename Function>
			void define(const std::string& name, Function&& func) {
				typedef utility::function_traits<Function> traits;

				auto funcPtr = new auto(std::forward<Function>(func));
				if (int result = sqlite3_create_function_v2(
					_db.get(), name.c_str(), traits::arity, SQLITE_UTF8, funcPtr,
					sql_function_binder::scalar<traits::arity, typename std::remove_reference<Function>::type>,
					nullptr, nullptr, [](void* ptr) {
						delete static_cast<decltype(funcPtr)>(ptr);
					}))
					errors::throw_sqlite_error(result);
			}

			template <typename StepFunction, typename FinalFunction>
			void define(const std::string& name, StepFunction&& step, FinalFunction && final) {
				typedef utility::function_traits<StepFunction> traits;
				using ContextType = typename std::remove_reference<typename traits::template argument<0>>::type;

				auto funcPtr = new auto(std::make_pair(std::forward<StepFunction>(step), std::forward<FinalFunction>(final)));
				if (int result = sqlite3_create_function_v2(
					_db.get(), name.c_str(), traits::arity - 1, SQLITE_UTF8, funcPtr, nullptr,
					sql_function_binder::step<ContextType, traits::arity, typename std::remove_reference<decltype(*funcPtr)>::type>,
					sql_function_binder::final<ContextType, typename std::remove_reference<decltype(*funcPtr)>::type>,
					[](void* ptr) {
						delete static_cast<decltype(funcPtr)>(ptr);
					}))
					errors::throw_sqlite_error(result);
			}

		};

		template<std::size_t Count>
		class binder {
		private:
			template <
				typename    Function,
				std::size_t Index
			>
				using nth_argument_type = typename utility::function_traits<
				Function
				>::template argument<Index>;

		public:
			// `Boundary` needs to be defaulted to `Count` so that the `run` function
			// template is not implicitly instantiated on class template instantiation.
			// Look up section 14.7.1 _Implicit instantiation_ of the ISO C++14 Standard
			// and the [dicussion](https://github.com/aminroosta/sqlite_modern_cpp/issues/8)
			// on Github.

			template<
				typename    Function,
				typename... Values,
				std::size_t Boundary = Count
			>
				static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run(
					database_binder& db,
					Function&& function,
					Values&&...      values
				) {
				typename std::remove_cv<typename std::remove_reference<nth_argument_type<Function, sizeof...(Values)>>::type>::type value{};
				get_col_from_db(db, sizeof...(Values), value);

				run<Function>(db, function, std::forward<Values>(values)..., std::move(value));
			}

			template<
				typename    Function,
				typename... Values,
				std::size_t Boundary = Count
			>
				static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run(
					database_binder&,
					Function&& function,
					Values&&...      values
				) {
				function(std::move(values)...);
			}
		};

		// int
		inline database_binder& operator<<(database_binder& db, const int& val) {
			int hresult;
			if ((hresult = sqlite3_bind_int(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}
			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const int& val) {
			sqlite3_result_int(db, val);
		}
		inline void get_col_from_db(database_binder& db, int inx, int& val) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				val = 0;
			}
			else {
				val = sqlite3_column_int(db._stmt.get(), inx);
			}
		}
		inline void get_val_from_db(sqlite3_value* value, int& val) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				val = 0;
			}
			else {
				val = sqlite3_value_int(value);
			}
		}

		// sqlite_int64
		inline database_binder& operator <<(database_binder& db, const sqlite_int64& val) {
			int hresult;
			if ((hresult = sqlite3_bind_int64(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}

			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const sqlite_int64& val) {
			sqlite3_result_int64(db, val);
		}
		inline void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				i = 0;
			}
			else {
				i = sqlite3_column_int64(db._stmt.get(), inx);
			}
		}
		inline void get_val_from_db(sqlite3_value* value, sqlite3_int64& i) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				i = 0;
			}
			else {
				i = sqlite3_value_int64(value);
			}
		}

		// float
		inline database_binder& operator <<(database_binder& db, const float& val) {
			int hresult;
			if ((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), double(val))) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}

			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const float& val) {
			sqlite3_result_double(db, val);
		}
		inline void get_col_from_db(database_binder& db, int inx, float& f) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				f = 0;
			}
			else {
				f = float(sqlite3_column_double(db._stmt.get(), inx));
			}
		}
		inline void get_val_from_db(sqlite3_value* value, float& f) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				f = 0;
			}
			else {
				f = float(sqlite3_value_double(value));
			}
		}

		// double
		inline database_binder& operator <<(database_binder& db, const double& val) {
			int hresult;
			if ((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}

			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const double& val) {
			sqlite3_result_double(db, val);
		}
		inline void get_col_from_db(database_binder& db, int inx, double& d) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				d = 0;
			}
			else {
				d = sqlite3_column_double(db._stmt.get(), inx);
			}
		}
		inline void get_val_from_db(sqlite3_value* value, double& d) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				d = 0;
			}
			else {
				d = sqlite3_value_double(value);
			}
		}

		// vector<T, A>
		template<typename T, typename A> inline database_binder& operator<<(database_binder& db, const std::vector<T, A>& vec) {
			void const* buf = reinterpret_cast<void const*>(vec.data());
			int bytes = vec.size() * sizeof(T);
			int hresult;
			if ((hresult = sqlite3_bind_blob(db._stmt.get(), db._next_index(), buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}
			return db;
		}
		template<typename T, typename A> inline void store_result_in_db(sqlite3_context* db, const std::vector<T, A>& vec) {
			void const* buf = reinterpret_cast<void const*>(vec.data());
			int bytes = vec.size() * sizeof(T);
			sqlite3_result_blob(db, buf, bytes, SQLITE_TRANSIENT);
		}
		template<typename T, typename A> inline void get_col_from_db(database_binder& db, int inx, std::vector<T, A>& vec) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				vec.clear();
			}
			else {
				int bytes = sqlite3_column_bytes(db._stmt.get(), inx);
				T const* buf = reinterpret_cast<T const*>(sqlite3_column_blob(db._stmt.get(), inx));
				vec = std::vector<T, A>(buf, buf + bytes / sizeof(T));
			}
		}
		template<typename T, typename A> inline void get_val_from_db(sqlite3_value* value, std::vector<T, A>& vec) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				vec.clear();
			}
			else {
				int bytes = sqlite3_value_bytes(value);
				T const* buf = reinterpret_cast<T const*>(sqlite3_value_blob(value));
				vec = std::vector<T, A>(buf, buf + bytes / sizeof(T));
			}
		}

		/* for nullptr support */
		inline database_binder& operator <<(database_binder& db, std::nullptr_t) {
			int hresult;
			if ((hresult = sqlite3_bind_null(db._stmt.get(), db._next_index())) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}
			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, std::nullptr_t) {
			sqlite3_result_null(db);
		}
		/* for nullptr support */
		template<typename T> inline database_binder& operator <<(database_binder& db, const std::unique_ptr<T>& val) {
			if (val)
				db << *val;
			else
				db << nullptr;
			return db;
		}

		/* for unique_ptr<T> support */
		template<typename T> inline void get_col_from_db(database_binder& db, int inx, std::unique_ptr<T>& _ptr_) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				_ptr_ = nullptr;
			}
			else {
				auto underling_ptr = new T();
				get_col_from_db(db, inx, *underling_ptr);
				_ptr_.reset(underling_ptr);
			}
		}
		template<typename T> inline void get_val_from_db(sqlite3_value* value, std::unique_ptr<T>& _ptr_) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				_ptr_ = nullptr;
			}
			else {
				auto underling_ptr = new T();
				get_val_from_db(value, *underling_ptr);
				_ptr_.reset(underling_ptr);
			}
		}

		// std::string
		inline void get_col_from_db(database_binder& db, int inx, std::string& s) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				s = std::string();
			}
			else {
				sqlite3_column_bytes(db._stmt.get(), inx);
				s = std::string(reinterpret_cast<char const*>(sqlite3_column_text(db._stmt.get(), inx)));
			}
		}
		inline void get_val_from_db(sqlite3_value* value, std::string& s) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				s = std::string();
			}
			else {
				sqlite3_value_bytes(value);
				s = std::string(reinterpret_cast<char const*>(sqlite3_value_text(value)));
			}
		}

		// Convert char* to string to trigger op<<(..., const std::string )
		template<std::size_t N> inline database_binder& operator <<(database_binder& db, const char(&STR)[N]) { return db << std::string(STR); }
		template<std::size_t N> inline database_binder& operator <<(database_binder& db, const char16_t(&STR)[N]) { return db << std::u16string(STR); }

		inline database_binder& operator <<(database_binder& db, const std::string& txt) {
			int hresult;
			if ((hresult = sqlite3_bind_text(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}

			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const std::string& val) {
			sqlite3_result_text(db, val.data(), -1, SQLITE_TRANSIENT);
		}
		// std::u16string
		inline void get_col_from_db(database_binder& db, int inx, std::u16string& w) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				w = std::u16string();
			}
			else {
				sqlite3_column_bytes16(db._stmt.get(), inx);
				w = std::u16string(reinterpret_cast<char16_t const*>(sqlite3_column_text16(db._stmt.get(), inx)));
			}
		}
		inline void get_val_from_db(sqlite3_value* value, std::u16string& w) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				w = std::u16string();
			}
			else {
				sqlite3_value_bytes16(value);
				w = std::u16string(reinterpret_cast<char16_t const*>(sqlite3_value_text16(value)));
			}
		}


		inline database_binder& operator <<(database_binder& db, const std::u16string& txt) {
			int hresult;
			if ((hresult = sqlite3_bind_text16(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) {
				errors::throw_sqlite_error(hresult, db.sql());
			}

			return db;
		}
		inline void store_result_in_db(sqlite3_context* db, const std::u16string& val) {
			sqlite3_result_text16(db, val.data(), -1, SQLITE_TRANSIENT);
		}

		// Other integer types
		template<class Integral, class = typename std::enable_if<std::is_integral<Integral>::value>::type>
		inline database_binder& operator <<(database_binder& db, const Integral& val) {
			return db << static_cast<sqlite3_int64>(val);
		}
		template<class Integral, class = std::enable_if<std::is_integral<Integral>::type>>
		inline void store_result_in_db(sqlite3_context* db, const Integral& val) {
			store_result_in_db(db, static_cast<sqlite3_int64>(val));
		}
		template<class Integral, class = typename std::enable_if<std::is_integral<Integral>::value>::type>
		inline void get_col_from_db(database_binder& db, int inx, Integral& val) {
			sqlite3_int64 i;
			get_col_from_db(db, inx, i);
			val = i;
		}
		template<class Integral, class = typename std::enable_if<std::is_integral<Integral>::value>::type>
		inline void get_val_from_db(sqlite3_value* value, Integral& val) {
			sqlite3_int64 i;
			get_val_from_db(value, i);
			val = i;
		}

		// std::optional support for NULL values
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
		template <typename OptionalT> inline database_binder& operator <<(database_binder& db, const optional<OptionalT>& val) {
			if (val) {
				return db << std::move(*val);
			}
			else {
				return db << nullptr;
			}
		}
		template <typename OptionalT> inline void store_result_in_db(sqlite3_context* db, const optional<OptionalT>& val) {
			if (val) {
				store_result_in_db(db, *val);
			}
			sqlite3_result_null(db);
		}

		template <typename OptionalT> inline void get_col_from_db(database_binder& db, int inx, optional<OptionalT>& o) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
				o = std::experimental::nullopt;
#else
				o.reset();
#endif
			}
			else {
				OptionalT v;
				get_col_from_db(db, inx, v);
				o = std::move(v);
			}
		}
		template <typename OptionalT> inline void get_val_from_db(sqlite3_value* value, optional<OptionalT>& o) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
				o = std::experimental::nullopt;
#else
				o.reset();
#endif
			}
			else {
				OptionalT v;
				get_val_from_db(value, v);
				o = std::move(v);
			}
		}
#endif

		// boost::optional support for NULL values
#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
		template <typename BoostOptionalT> inline database_binder& operator <<(database_binder& db, const boost::optional<BoostOptionalT>& val) {
			if (val) {
				return db << std::move(*val);
			}
			else {
				return db << nullptr;
			}
		}
		template <typename BoostOptionalT> inline void store_result_in_db(sqlite3_context* db, const boost::optional<BoostOptionalT>& val) {
			if (val) {
				store_result_in_db(db, *val);
			}
			sqlite3_result_null(db);
		}

		template <typename BoostOptionalT> inline void get_col_from_db(database_binder& db, int inx, boost::optional<BoostOptionalT>& o) {
			if (sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) {
				o.reset();
			}
			else {
				BoostOptionalT v;
				get_col_from_db(db, inx, v);
				o = std::move(v);
			}
		}
		template <typename BoostOptionalT> inline void get_val_from_db(sqlite3_value* value, boost::optional<BoostOptionalT>& o) {
			if (sqlite3_value_type(value) == SQLITE_NULL) {
				o.reset();
			}
			else {
				BoostOptionalT v;
				get_val_from_db(value, v);
				o = std::move(v);
			}
		}
#endif

#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
		template <typename ...Args> inline database_binder& operator <<(database_binder& db, const std::variant<Args...>& val) {
			std::visit([&](auto&& opt) {db << std::forward<decltype(opt)>(opt); }, val);
			return db;
		}
		template <typename ...Args> inline void store_result_in_db(sqlite3_context* db, const std::variant<Args...>& val) {
			std::visit([&](auto&& opt) {store_result_in_db(db, std::forward<decltype(opt)>(opt)); }, val);
		}
		template <typename ...Args> inline void get_col_from_db(database_binder& db, int inx, std::variant<Args...>& val) {
			utility::variant_select<Args...>(sqlite3_column_type(db._stmt.get(), inx))([&](auto v) {
				get_col_from_db(db, inx, v);
				val = std::move(v);
				});
		}
		template <typename ...Args> inline void get_val_from_db(sqlite3_value* value, std::variant<Args...>& val) {
			utility::variant_select<Args...>(sqlite3_value_type(value))([&](auto v) {
				get_val_from_db(value, v);
				val = std::move(v);
				});
		}
#endif

		// Some ppl are lazy so we have a operator for proper prep. statemant handling.
		void inline operator++(database_binder& db, int) { db.execute(); }

		// Convert the rValue binder to a reference and call first op<<, its needed for the call that creates the binder (be carefull of recursion here!)
		template<typename T> database_binder&& operator << (database_binder&& db, const T& val) { db << val; return std::move(db); }

		namespace sql_function_binder {
			template<class T>
			struct AggregateCtxt {
				T obj;
				bool constructed = true;
			};

			template<
				typename ContextType,
				std::size_t Count,
				typename    Functions
			>
				inline void step(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals
				) {
				auto ctxt = static_cast<AggregateCtxt<ContextType>*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt<ContextType>)));
				if (!ctxt) return;
				try {
					if (!ctxt->constructed) new(ctxt) AggregateCtxt<ContextType>();
					step<Count, Functions>(db, count, vals, ctxt->obj);
					return;
				}
				catch (sqlite_exception& e) {
					sqlite3_result_error_code(db, e.get_code());
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (std::exception& e) {
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (...) {
					sqlite3_result_error(db, "Unknown error", -1);
				}
				if (ctxt && ctxt->constructed)
					ctxt->~AggregateCtxt();
			}

			template<
				std::size_t Count,
				typename    Functions,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals,
					Values&&...      values
				) {
				typename std::remove_cv<
					typename std::remove_reference<
					typename utility::function_traits<
					typename Functions::first_type
							>::template argument<sizeof...(Values)>
					>::type
				>::type value{};
				get_val_from_db(vals[sizeof...(Values) - 1], value);

				step<Count, Functions>(db, count, vals, std::forward<Values>(values)..., std::move(value));
			}

			template<
				std::size_t Count,
				typename    Functions,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step(
					sqlite3_context* db,
					int,
					sqlite3_value**,
					Values&&...      values
				) {
				static_cast<Functions*>(sqlite3_user_data(db))->first(std::forward<Values>(values)...);
			}

			template<
				typename    ContextType,
				typename    Functions
			>
				inline void final(sqlite3_context* db) {
				auto ctxt = static_cast<AggregateCtxt<ContextType>*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt<ContextType>)));
				try {
					if (!ctxt) return;
					if (!ctxt->constructed) new(ctxt) AggregateCtxt<ContextType>();
					store_result_in_db(db,
						static_cast<Functions*>(sqlite3_user_data(db))->second(ctxt->obj));
				}
				catch (sqlite_exception& e) {
					sqlite3_result_error_code(db, e.get_code());
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (std::exception& e) {
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (...) {
					sqlite3_result_error(db, "Unknown error", -1);
				}
				if (ctxt && ctxt->constructed)
					ctxt->~AggregateCtxt();
			}

			template<
				std::size_t Count,
				typename    Function,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar(
					sqlite3_context* db,
					int              count,
					sqlite3_value** vals,
					Values&&...      values
				) {
				typename std::remove_cv<
					typename std::remove_reference<
					typename utility::function_traits<Function>::template argument<sizeof...(Values)>
					>::type
				>::type value{};
				get_val_from_db(vals[sizeof...(Values)], value);

				scalar<Count, Function>(db, count, vals, std::forward<Values>(values)..., std::move(value));
			}

			template<
				std::size_t Count,
				typename    Function,
				typename... Values
			>
				inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar(
					sqlite3_context* db,
					int,
					sqlite3_value**,
					Values&&...      values
				) {
				try {
					store_result_in_db(db,
						(*static_cast<Function*>(sqlite3_user_data(db)))(std::forward<Values>(values)...));
				}
				catch (sqlite_exception& e) {
					sqlite3_result_error_code(db, e.get_code());
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (std::exception& e) {
					sqlite3_result_error(db, e.what(), -1);
				}
				catch (...) {
					sqlite3_result_error(db, "Unknown error", -1);
				}
			}
		}
	}
}